--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit a5aa2097bd7370c3c16bb1fb670b195f42c9b25e
Parents : dc43bc6
Author : probability <v6z777@protonmail.com>
Date : 2024-11-25T18:15:42-05:00
Add Checkbox and Radio Button fields to Micron format
Changes
4 files changed, 193 insertions(+), 56 deletions(-)
Diff
diff --git a/nomadnet/examples/various/input_fields.py b/nomadnet/examples/various/input_fields.py
index 43d7e65..663dc93 100644
--- a/nomadnet/examples/various/input_fields.py
+++ b/nomadnet/examples/various/input_fields.py
@@ -17,6 +17,8 @@ The following section contains a simple set of fields, and a few different links
-=
+
+>>>Text Fields
An input field : `B444`<username`Entered data>`b
An masked field : `B444`<!|password`Value of Field>`b
@@ -27,7 +29,24 @@ Two fields : `B444`<8|one`One>`b `B444`<8|two`Two>`b
The data can be `!`[submitted`:/page/input_fields.mu`username|two]`!.
-You can `!`[submit`:/page/input_fields.mu`one|password|small]`! other fields, or just `!`[a single one`:/page/input_fields.mu`username]`!
+>> Checkbox Fields
+
+`B444`<?|sign_up|1`>`b Sign me up
+
+>> Radio group
+
+Select your favorite color:
+
+`B900`<^|color|Red`>`b Red
+
+`B090`<^|color|Green`>`b Green
+
+`B009`<^|color|Blue`>`b Blue
+
+
+>>> Submitting data
+
+You can `!`[submit`:/page/input_fields.mu`one|password|small|color]`! other fields, or just `!`[a single one`:/page/input_fields.mu`username]`!
Or simply `!`[submit them all`:/page/input_fields.mu`*]`!.
diff --git a/nomadnet/ui/textui/Browser.py b/nomadnet/ui/textui/Browser.py
index 58ae008..5e412a4 100644
--- a/nomadnet/ui/textui/Browser.py
+++ b/nomadnet/ui/textui/Browser.py
@@ -180,23 +180,47 @@ class Browser:
else:
link_fields.append(e)
- def recurse_down(w):
- target = None
- if isinstance(w, list):
- for t in w:
- recurse_down(t)
- elif isinstance(w, tuple):
- for t in w:
- recurse_down(t)
- elif hasattr(w, "contents"):
- recurse_down(w.contents)
- elif hasattr(w, "original_widget"):
- recurse_down(w.original_widget)
- elif hasattr(w, "_original_widget"):
- recurse_down(w._original_widget)
- else:
- if hasattr(w, "field_name") and (all_fields or w.field_name in link_fields):
- request_data["field_"+w.field_name] = w.get_edit_text()
+ def recurse_down(w):
+ if isinstance(w, list):
+ for t in w:
+ recurse_down(t)
+ elif isinstance(w, tuple):
+ for t in w:
+ recurse_down(t)
+ elif hasattr(w, "contents"):
+ recurse_down(w.contents)
+ elif hasattr(w, "original_widget"):
+ recurse_down(w.original_widget)
+ elif hasattr(w, "_original_widget"):
+ recurse_down(w._original_widget)
+ else:
+ if hasattr(w, "field_name") and (all_fields or w.field_name in link_fields):
+ field_key = "field_" + w.field_name
+ if isinstance(w, urwid.Edit):
+ request_data[field_key] = w.edit_text
+ elif isinstance(w, urwid.RadioButton):
+ if w.state:
+ user_data = getattr(w, "field_value", None)
+ if user_data is not None:
+ request_data[field_key] = user_data
+ elif isinstance(w, urwid.CheckBox):
+ user_data = getattr(w, "field_value", "1")
+ if w.state:
+ existing_value = request_data.get(field_key, '')
+ if existing_value:
+ # Concatenate the new value with the existing one
+ request_data[field_key] = existing_value + ',' + user_data
+ else:
+ # Initialize the field with the current value
+ request_data[field_key] = user_data
+ else:
+ pass # do nothing if checkbox is not check
+
+
+
+
+
+
recurse_down(self.attr_maps)
RNS.log("Including request data: "+str(request_data), RNS.LOG_DEBUG)
diff --git a/nomadnet/ui/textui/Guide.py b/nomadnet/ui/textui/Guide.py
index 69305cf..48dfb56 100644
--- a/nomadnet/ui/textui/Guide.py
+++ b/nomadnet/ui/textui/Guide.py
@@ -1152,6 +1152,43 @@ A sized input field: `B444`<16|with_size`>`B333
A masked input field: `B444`<!|masked_demo`hidden text>`B333
Full control: `B444`<!32|all_options`hidden text>`B333
+`b
+>>> Checkboxes
+
+In addition to text fields, Checkboxes are another way of submitting data. They allow the user to make a single selection or select multiple options.
+
+`Faaa
+`=
+`<?|field_name|value`>`b Label Text`
+`=
+When the checkbox is checked, it's field will be set to the provided value. If there are multiple checkboxes that share the same field name, the checked values will be concatenated when they are sent to the node by a comma.
+``
+
+`B444`<?|sign_up|1`>`b Sign me up`
+
+>>> Radio groups
+
+Radio groups are another input that lets the user chose from a set of options. Unlike checkboxes, radio buttons with the same field name are mutually exclusive.
+
+Example:
+
+`=
+`B900`<^|color|Red`>`b Red
+
+`B090`<^|color|Green`>`b Green
+
+`B009`<^|color|Blue`>`b Blue
+`=
+
+will render:
+
+`B900`<^|color|Red`>`b Red
+
+`B090`<^|color|Green`>`b Green
+
+`B009`<^|color|Blue`>`b Blue
+
+In this example, when the data is submitted, `B444` field_color`b will be set to whichever value from the list was selected.
``
diff --git a/nomadnet/ui/textui/MicronParser.py b/nomadnet/ui/textui/MicronParser.py
index 27e420b..7668ecf 100644
--- a/nomadnet/ui/textui/MicronParser.py
+++ b/nomadnet/ui/textui/MicronParser.py
@@ -160,7 +160,6 @@ def parse_line(line, state, url_delegate):
tw.in_columns = True
else:
tw = urwid.Text(o, align=state["align"])
-
widgets.append((urwid.PACK, tw))
else:
if o["type"] == "field":
@@ -169,10 +168,37 @@ def parse_line(line, state, url_delegate):
fn = o["name"]
fs = o["style"]
fmask = "*" if o["masked"] else None
- f = urwid.Edit(caption="", edit_text=fd, align=state["align"], multiline=True, mask=fmask)
+ f = urwid.Edit(caption="", edit_text=fd, align=state["align"], multiline=False, mask=fmask)
f.field_name = fn
fa = urwid.AttrMap(f, fs)
widgets.append((fw, fa))
+ elif o["type"] == "checkbox":
+ fn = o["name"]
+ fv = o["value"]
+ flabel = o["label"]
+ fs = o["style"]
+ f = urwid.CheckBox(flabel, state=False)
+ f.field_name = fn
+ f.field_value = fv
+ fa = urwid.AttrMap(f, fs)
+ widgets.append((urwid.PACK, fa))
+ elif o["type"] == "radio":
+ fn = o["name"]
+ fv = o["value"]
+ flabel = o["label"]
+ fs = o["style"]
+ if not "radio_groups" in state:
+ state["radio_groups"] = {}
+ if not fn in state["radio_groups"]:
+ state["radio_groups"][fn] = []
+ group = state["radio_groups"][fn]
+ f = urwid.RadioButton(group, flabel, state=False, user_data=fv)
+ f.field_name = fn
+ f.field_value = fv
+ fa = urwid.AttrMap(f, fs)
+ widgets.append((urwid.PACK, fa))
+
+
columns_widget = urwid.Columns(widgets, dividechars=0)
text_widget = columns_widget
@@ -458,54 +484,85 @@ def make_output(state, line, url_delegate):
elif c == "a":
state["align"] = state["default_align"]
- elif c == "<":
+ elif c == '<':
+ if len(part) > 0:
+ output.append(make_part(state, part))
+ part = ""
try:
- field_name = None
- field_name_end = line[i:].find("`")
- if field_name_end == -1:
- pass
+ field_start = i + 1 # position after '<'
+ backtick_pos = line.find('`', field_start)
+ if backtick_pos == -1:
+ pass # No '`', invalid field
else:
- field_name = line[i+1:i+field_name_end]
- field_name_skip = len(field_name)
+ field_content = line[field_start:backtick_pos]
field_masked = False
field_width = 24
-
- if "|" in field_name:
- f_components = field_name.split("|")
+ field_type = "field"
+ field_name = field_content
+ field_value = ""
+ field_data = ""
+
+ # check if field_content contains '|'
+ if '|' in field_content:
+ f_components = field_content.split('|')
field_flags = f_components[0]
field_name = f_components[1]
- if "!" in field_flags:
+
+ # handle field type indicators
+ if '^' in field_flags:
+ field_type = "radio"
+ field_flags = field_flags.replace("^", "")
+ elif '?' in field_flags:
+ field_type = "checkbox"
+ field_flags = field_flags.replace("?", "")
+ elif '!' in field_flags:
field_flags = field_flags.replace("!", "")
field_masked = True
+
+ # Handle field width
if len(field_flags) > 0:
- field_width = min(int(field_flags), 256)
-
- def sr():
- return "@{"+str(random.randint(1000,9999))+"}"
- rsg = sr()
- while rsg in line[i+field_name_end:]:
- rsg = sr()
- lr = line[i+field_name_end:].replace("\\>", rsg)
- endpos = lr.find(">")
-
- if endpos == -1:
- pass
-
+ try:
+ field_width = min(int(field_flags), 256)
+ except ValueError:
+ pass # Ignore invalid width
+
+ if len(f_components) > 2:
+ field_value = f_components[2]
+ else:
+ field_name = field_content
+
+ # Find the closing '>' character
+ field_end = line.find('>', backtick_pos)
+ if field_end == -1:
+ pass # No closing '>', invalid field
else:
- field_data = lr[1:endpos].replace(rsg, "\\>")
- skip = len(field_data)+field_name_skip+2
- field_data = field_data.replace("\\>", ">")
- output.append({
- "type":"field",
- "name": field_name,
- "width": field_width,
- "masked": field_masked,
- "data": field_data,
- "style": make_style(state)
- })
+ field_data = line[backtick_pos+1:field_end]
+
+ # Now, we have all field data
+ if field_type in ["checkbox", "radio"]:
+ # for checkboxes and radios, field_data is the label
+ output.append({
+ "type": field_type,
+ "name": field_name,
+ "value": field_value if field_value else field_data,
+ "label": field_data,
+ "style": make_style(state)
+ })
+ else:
+ # For text fields field_data is the initial text
+ output.append({
+ "type": "field",
+ "name": field_name,
+ "width": field_width,
+ "masked": field_masked,
+ "data": field_data,
+ "style": make_style(state)
+ })
+ skip = field_end - i
except Exception as e:
pass
-
+
+
elif c == "[":
endpos = line[i:].find("]")
if endpos == -1:
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────